package opt

import (
	"bufio"
	"github.com/pkg/errors"
	"math"
	"os"
)

type FileWriter struct {
	f      *os.File
	pool   *bufio.Writer
	pos    uint64
	name   string
	append bool
}

func NewFileWriter(filePath string, append bool) (*FileWriter, error) {
	var (
		f   *os.File
		err error
	)
	if append {
		f, err = os.OpenFile(filePath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
	} else {
		f, err = os.OpenFile(filePath, os.O_CREATE|os.O_RDWR, 0666)
	}
	if err != nil {
		return nil, err
	}
	return &FileWriter{
		f:      f,
		pool:   bufio.NewWriterSize(f, 1<<22),
		pos:    0,
		name:   filePath,
		append: append,
	}, nil
}

func (fw *FileWriter) WriteString(bufs ...string) error {
	for _, b := range bufs {
		n, err := fw.pool.WriteString(b)
		fw.pos += uint64(n)
		if err != nil {
			return err
		}
		if fw.pos > 16*math.MaxUint32 {
			return errors.Errorf("%q exceeding max size of 64GiB", fw.name)
		}
	}
	fw.pool.WriteString("\n")
	return nil
}

func (fw *FileWriter) Write(bufs ...[]byte) error {
	for _, b := range bufs {
		n, err := fw.pool.Write(b)
		fw.pos += uint64(n)
		if err != nil {
			return err
		}
		if fw.pos > 16*math.MaxUint32 {
			return errors.Errorf("%q exceeding max size of 64GiB", fw.name)
		}
	}
	fw.pool.WriteString("\n")
	return nil
}

func (fw *FileWriter) Flush() error {
	return fw.pool.Flush()
}

func (fw *FileWriter) WriteAt(buf []byte, pos uint64) error {
	if fw.append {
		return errors.Errorf("%q APPEND MODE", fw.name)
	}
	if err := fw.Flush(); err != nil {
		return err
	}
	_, err := fw.f.WriteAt(buf, int64(pos))
	return err
}

func (fw *FileWriter) AddPadding(size int) error {
	p := fw.pos % uint64(size)
	if p == 0 {
		return nil
	}
	p = uint64(size) - p

	if err := fw.Write(make([]byte, p)); err != nil {
		return errors.Wrap(err, "add padding")
	}
	return nil
}

func (fw *FileWriter) Close() error {
	if err := fw.Flush(); err != nil {
		return err
	}
	if err := fw.f.Sync(); err != nil {
		return err
	}
	return fw.f.Close()
}

func (fw *FileWriter) Remove() error {
	return os.Remove(fw.name)
}

func (fw *FileWriter) Pos() uint64 {
	return fw.pos
}
